package engine.gameObject.collider;

import org.lwjgl.util.vector.Vector3f;

import engine.gameObject.GameObject;

public abstract class Collider {

	/**
	 * This function is called to check, whether there is a collision. G1 is the object, which initiated the test and g2 ist the object to check against. 
	 * Thus, g1.getCollider() == this
	 * @param g1
	 * @param g2
	 * @return
	 */
	public abstract boolean collides(GameObject g1, GameObject g2);
	
	public static boolean sphereInSphere(float[] pos1, float radius1, float[] pos2, float radius2) {
		return sphereInSphere(pos1[0], pos1[1], pos1[2], radius1, pos2[0], pos2[1], pos2[2], radius2);
	}
	
	public static boolean sphereInSphere(float posX1, float posY1, float posZ1, float radius1, float posX2, float posY2, float posZ2, float radius2) {
		float squareDistance = ( posX1 - posX2) * ( posX1 - posX2) + 
				(posY1 - posY2) * (posY1 - posY2) + 
				(posZ1 - posZ2) * (posZ1 - posZ2);
		
		return squareDistance <= (radius1 + radius2) * (radius1 + radius2);
	}
	
	public static boolean sphereInPolygon(float[] position, float radius, float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3) {
		Vector3f v1 = new Vector3f(x2 - x1, y2 - y1, z2 - z1);
		Vector3f v2 = new Vector3f(x3 - x1, y3 - y1, z3 - z1);
		
		Vector3f center = new Vector3f(position[0], position[1], position[2]);
		
		Vector3f normal = Vector3f.cross(v1, v2, null);
		float a = -(normal.getX() * x1 + normal.getY() * y1 + normal.getZ() * z1);
		
		float aboveFrac = Vector3f.dot(normal, center) + a;
		float belowFrac = normal.length();
		float distanceSpherePlane = aboveFrac / belowFrac;
		
		if(Math.abs(distanceSpherePlane) < radius) {
			normal.normalise();
			normal.x *= distanceSpherePlane;
			normal.y *= distanceSpherePlane;
			normal.z *= distanceSpherePlane;
			Vector3f centerOnPlane = Vector3f.add(center, normal, null);
			
			aboveFrac = (centerOnPlane.getY() - y1) * v1.getX() - (centerOnPlane.getX() - x1) * v1.getY();
			belowFrac = v2.getY() * v1.getX() - v2.getX() * v1.getY();
			float t = aboveFrac / belowFrac;
			float s = ((centerOnPlane.getX() - x1) - v2.getX() * t) / v1.getX();
			
			if(0 <= s + t && s + t <= 1) {
				return true;
			} else {
				//check distance to ray 1
				float radius2 = (float)Math.sqrt(radius * radius - distanceSpherePlane * distanceSpherePlane);
				if(circleInRay(centerOnPlane, radius2, new Vector3f(x1, y1, z1), v1)) {
					return true;
				}
				
				//check distance to ray 2
				Vector3f v3 = new Vector3f(x2 - x3, y2 - y3, z2 - z3);
				if(circleInRay(centerOnPlane, radius2, new Vector3f(x2, y2, z2), v3)) {
					return true;
				}
				
				//check distance to ray 3
				if(circleInRay(centerOnPlane, radius2, new Vector3f(x3, y3, z3), v2)) {
					return true;
				}
				
				return false;
			}
		}
		
		return false;
	}
	
	public static boolean polygonInBox(Vector3f C, float a, Vector3f U0, Vector3f U1, Vector3f U2) {
		Vector3f A0, A1, A2, E0, E1, E2, N, D;
		
		A0 = new Vector3f(1, 0, 0);
		A1 = new Vector3f(0, 1, 0);
		A2 = new Vector3f(0, 0, 1);
		
		E0 = Vector3f.sub(U1, U0, null);
		E1 = Vector3f.sub(U2, U0, null);
		E2 = Vector3f.sub(U2, U1, null);
		
		N = Vector3f.cross(E0, E1, null);
		
		D = Vector3f.sub(U0, C, null);
		
		Vector3f[] list = new Vector3f[]{N, A0, A1, A2, 
				Vector3f.cross(A0, E0, null), Vector3f.cross(A0, E1, null), Vector3f.cross(A0, E2, null), 
				Vector3f.cross(A1, E0, null), Vector3f.cross(A1, E1, null), Vector3f.cross(A1, E2, null),
				Vector3f.cross(A2, E0, null), Vector3f.cross(A2, E1, null), Vector3f.cross(A2, E2, null)};
		
		for(Vector3f L : list) {
			float R = a * (Math.abs(L.getX()) + Math.abs(L.getY()) + Math.abs(L.getZ()));
			float p0 = Vector3f.dot(L, D);
			float p1 = p0 + Vector3f.dot(L, E0);
			float p2 = p0 + Vector3f.dot(L, E1);
			
			if(Math.min(Math.min(p0, p1), p2) > R || Math.max(Math.max(p0, p1), p2) < -R) {
				return false;
			}
		}
		
		return true;
	}
	
	private static boolean circleInRay(Vector3f center, float radius, Vector3f point, Vector3f direction) {
		Vector3f p = Vector3f.cross(direction, Vector3f.sub(center, point, null), null);
		
		float distance = p.length() / direction.length();
		
		return distance < radius;
	}
	
	public abstract Collider copy();
	
	public abstract void reset();
}
